// Copyright (c) 2013-2014 by Kris Maglione <maglione.k@gmail.com>
//
// This work is licensed for reuse under an MIT license. Details are
// given in the LICENSE file included with this file.
"use strict";

var EXPORTED_SYMBOLS = ["TESTS", "media", "image"];

var { config } = require("config");
var { DOM, XHTML, XUL } = require("dom");
var { prefs } = require("prefs");
var { template, util } = require("util");

function ABOUT(page) "@mozilla.org/network/protocol/about;1?what=" + page;

lazyRequire("io", ["File", "io"]);
lazyRequire("messages", ["_"]);

function media(name) "theme-test://content/media/" + name;
function image(name) media(name + ".png");

const OSX_BASE1 = "chrome://browser/skin/";
const OSX_BASE2 = "chrome://browser/skin/lion/";
const OSX_MAGIC = [
    "keyhole-circle.png",
    "Toolbar.png",
    "toolbarbutton-dropmarker.png",
    "tabbrowser/alltabs-box-bkgnd-icon.png",
    "tabview/tabview.png",
    "places/toolbar.png ",
];

const TESTS = {
    "Sidebars": {},
    "  Bookmarks": {
        sidebar: "viewBookmarksSidebar"
    },
    "  History": {
        sidebar: "viewHistorySidebar"
    },
    "Toolbars": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Various common toolbar permutations must be \
                tested, ideally each with and without small \
                icons and tabs on top."],
        ],
    },
    "  Open all": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                All toolbars must be appropriately formatted, \
                with all icons properly displayed. This should \
                be checked with and without tabs on top and the \
                menu bar hidden."],
        ],
        action: function action() {
            let undo = this.undo.toolbars;
            this.forEachToolbar(function (tb) {
                if (tb.getAttribute("type") != "menubar") {
                    undo[tb.id] = tb.collapsed;
                    this.window.setToolbarVisibility(tb, true);
                }
            }, true);
        }
    },
    "  Hidden menubar": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Navigation bar must have a properly styled appmenu \
                button. Should be tested with and without tabs on top."],
        ],
        action: function action() {
            let undo = this.undo.toolbars;
            this.forEachToolbar(function (tb) {
                if (tb.parentNode == this.window.gNavToolbox) {
                    undo[tb.id] = tb.collapsed;
                    this.window.setToolbarVisibility(tb, ~["nav-bar", "TabsToolbar"].indexOf(tb.id));
                }
            }, true);
        },
        test: function test() this.window.document.querySelector("#appmenu-toolbar-button")
    },
    "Important web pages": {},
    "  Mozilla's Mission": {
        content: ["div", { xmlns: "html" },
            ["p", {},
                "Video controls must be properly formatted and useable."],
        ],
        action: function action() {
            this.loadPage("https://www.mozilla.org/mission/");
        }
    },
    "  Normal": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Plain HTTP connections must have a simple globe, or some \
                other icon clearly indicating that the connection is not \
                entirely secure, in their identity boxes and nothing \
                else. They must, under no circumstances, display a \
                padlock icon."],
            ["img", { src: image("ff14/normal-box") }],
        ],
        action: function action() {
            this.loadPage("http://example.com/", function () {
                this.openIdentityBox();
            });
        }
    },
    "  Mixed": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                HTTPS sites with mixed content must have a simple globe, \
                or some other icon clearly indicating that the \
                connection is not entirely secure, in their identity \
                box. They must, under no circumstances, display a \
                padlock icon. The URL bar should have a slightly \
                different background color than plain HTTP connections \
                or secure connections would."],
            ["img", { src: image("ff14/mixed-box") }],
        ],
        action: function action() {
            this.loadPage("https://people.mozilla.com/~kmaglione/mixed-content.html", function () {
                this.openIdentityBox();
            });
        }
    },
    "  Secure": {
        content: ["div", { xmlns: "html" },
            config.haveGecko("14") ?
                [["p", {}, "\
                    Secure sites must have a padlock icon in the \
                    identity box and nothing else."],
                 ["img", { src: image("ff14/secure-box") }]]
            :
                [["p", {}, "\
                    Secure web pages must have a distinctively colored \
                    identity box containing the site's hostname, and \
                    a properly formatted identity panel:"],
                 ["img", { src: image("secure") }]],
        ],
        action: function action() {
            this.loadPage("https://www.google.com/", function () {
                this.openIdentityBox();
            });
        }
    },
    "  Extended validation": {
        content: ["div", { xmlns: "html" },
            config.haveGecko("14") ?
                [["p", {}, "\
                    Secure web pages with Extended Validation \
                    certificates must have an identity box with a \
                    distinct background color, a padlock icon, and the \
                    identity of the site owner clearly displayed."],
                 ["img", { src: image("ff14/ev-box") }]]
            :
                [["p", {}, "\
                    Secure web pages with Extended Validation certificates \
                    must have a distinctively colored identity box, markedly \
                    different from the ordinary secure identity box, \
                    containing the name of the site's owner, and a \
                    properly formatted identity panel:"],
                 ["img", { src: image("ev") }]],
        ],
        action: function action() {
            this.loadPage("https://addons.mozilla.org/", function () {
                this.openIdentityBox();
            });
        }
    },
    "  Malware": {
        content: ["div", { xmlns: "html" },
            ["p", {},
                "The malware detected page must be functional and properly styled:"],
            ["img", { src: image("malware") }],
        ],
        action: function action() {
            this.loadPage("https://www.mozilla.com/firefox/its-an-attack.html");
        }
    },
    "  Phishing": {
        content: ["div", { xmlns: "html" },
            ["p", {},
                "The phishing detected page must be functional and properly styled:"],
            ["img", { src: image("phishing") }],
        ],
        action: function action() {
            this.loadPage("https://www.mozilla.com/firefox/its-a-trap.html");
        }
    },
    "  Untrusted": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                The untrusted connection page must be functional and properly styled:"],
            ["img", { src: image("untrusted") }],
        ],
        action: function action() {
            this.loadPage("about:certerror?e=nssBadCert&u=https%3A//sort-of-secure-site.com/&c=UTF-8&d=This%20page%20is%20insecure%20because...%20because%20I%20say%20so!");
        }
    },
    "  FTP": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be useable."],
        ],
        action: function action() {
            this.loadPage("ftp://ftp.mozilla.org/pub");
        }
    },
    "  Local files": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be useable."],
        ],
        action: function action() {
            let dir = File(services.directory.get("Home", Ci.nsIFile))
            this.loadPage(dir.URI.spec);
        }
    },
    "about: pages": {},
    "  about:": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be functional and readable, and must show the \
                appropriate version and logo for the current release \
                channel."],
        ],
        action: function action() {
            this.loadPage("about:");
        }
    },
    "  about:addons": {
        content: ["div", { xmlns: "html" },
            ["ul", {},
                ["li", {}, "Must be functional and it must be possible to disable \
                    the theme."],
                ["li", {}, "Disabled add-ons must be clearly distinguishable from \
                    enabled add-ons."],
                ["li", {}, "Alerts for incompatible add-ons and messages about \
                    what will occur after a restart should be tested as \
                    well."]],
        ],
        action: function action() {
            this.loadPage("about:addons");
        }
    },
    "  about:app-manager": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be functional."],
        ],
        action: function action() {
            this.loadPage("about:app-manager");
        },
        test: function test() ABOUT("app-manager") in Cc
    },
    "  about:compartments": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be functional and readable."],
        ],
        action: function action() {
            this.loadPage("about:compartments");
        },
        test: function test() !config.haveGecko("26")
    },
    "  about:config (warning)": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must display a functional “I'll be careful” screen."],
            ["img", { src: image("careful") }],
        ],
        action: function action() {
            this.setPref("general.warnOnAboutConfig", true);
            this.loadPage("about:config");
        }
    },
    "  about:memory": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be functional and readable."],
        ],
        action: function action() {
            this.loadPage("about:memory");
        }
    },
    "  about:networking": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be functional."],
        ],
        action: function action() {
            this.setPref("network.warnOnAboutNetworking", false);
            this.loadPage("about:networking");
        },
        test: function test() ABOUT("networking") in Cc
    },
    "  about:networking (warning)": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must display a properly formatted and functional warning screen."],
        ],
        action: function action() {
            this.setPref("network.warnOnAboutNetworking", true);
            this.loadPage("about:networking");
        },
        test: function test() ABOUT("networking") in Cc
    },
    "  about:permissions": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be properly formatted and functional."],
        ],
        action: function action() {
            this.loadPage("about:permissions");
        }
    },
    "  about:preferences": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be properly formatted and functional."],
        ],
        action: function action() {
            this.loadPage("about:preferences");
        }
    },
    "Developer tools": {},
    "  Debugger": {
        devToolsPane: true,
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.loadPage("about:home", function (browser) {
                this.openToolbox("jsdebugger");
            });
        },
        test: function test() "gDevTools" in this.window
    },
    "  Element inspector": {
        devToolsPane: true,
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.loadPage("about:home", function (browser) {
                let { contentDocument: doc } = browser;
                let logo = DOM("#brandStartLogo, #brandLogo", doc);
                this.inspectNode(logo[0]);
            });
        },
        cleanup: function cleanup() {
            if (this.window.InspectorUI)
                this.window.InspectorUI.closeInspectorUI();
        },
        test: function test() "InspectorUI" in this.window || "gDevTools" in this.window
    },
    "  Element inspector (with panels)": {
        content: ["div", { xmlns: "html" },
            ["ul", {},
                ["li", {}, "Must be formatted properly formatted and functional."],
                ["li", {}, "Style rules must be shown for the element's \
                    style attribute and the page's styleheet."],
                ["li", {}, "Both ", ["tt", {}, "Rules"], " and ",
                    ["tt", {}, "Computed"], " style panels \
                    should be checked for formatting and functionality. \
                    Style values must be editable, it must be possible to \
                    add new style properties."],
                ["li", {}, "Attribute values must be editable in the HTML panel."]],
        ],
        action: function action() {
            this.loadPage("about:home", function (browser) {
                let { contentDocument: doc } = browser;
                let { InspectorUI } = this.window;

                DOM("head", doc).append(["style", { type: "text/css", xmlns: "html" },
                   "#brandStartLogo { visibility: visible; text-align: left; }"]);

                let logo = DOM("#brandStartLogo, #brandLogo", doc)
                    .css({ whiteSpace: "pre-wrap",
                           display: "inline" });

                InspectorUI.openInspectorUI(logo[0]);
                if (InspectorUI.showSidebar)
                    InspectorUI.showSidebar();
                else if (InspectorUI.sidebar)
                    InspectorUI.sidebar.show();
                else if (InspectorUI.toggleSidebar)
                    InspectorUI.toggleSidebar();

                if (InspectorUI.treePanel)
                    InspectorUI.treePanel.open();
                else if (InspectorUI.toggleHTMLPanel)
                    InspectorUI.toggleHTMLPanel();
            });
        },
        cleanup: function cleanup() {
            this.window.InspectorUI.closeInspectorUI();
        },
        test: function test() "InspectorUI" in this.window
    },
    "  Network": {
        devToolsPane: true,
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.loadPage("http://example.com/", function (browser) {
                this.openToolbox("netmonitor", () => {
                    util.timeout(() => {
                        browser.reload();
                    }, 100);
                });
            });
        },
        test: function test() "gDevTools" in this.window
    },
    "  Profiler": {
        devToolsPane: true,
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.loadPage("about:home", function (browser) {
                this.openToolbox("jsprofiler");
            });
        },
        test: function test() "gDevTools" in this.window
    },
    "  Responsive design view": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            let { ResponsiveUI, gBrowser } = this.window;
            let tab = gBrowser.selectedTab;
            if (!ResponsiveUI.ResponsiveUIManager.isActiveForTab(tab)) {
                ResponsiveUI.toggle();
                this.undo.cleanup.push(() => {
                    if (ResponsiveUI.ResponsiveUIManager.isActiveForTab(tab))
                        ResponsiveUI.ResponsiveUIManager.toggle(this.window, tab);
                });
            }
        },
        test: function test() "ResponsiveUI" in this.window
    },
    "  Style editor": {
        devToolsPane: true,
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.openToolbox("styleeditor", () => {
                // Make Alt+n work as expected, rather than creating a
                // new stylesheet
                for each (let t in [100, 500, 1000])
                    this.window.setTimeout(function () {
                        this.content.focus();
                    }, t);
            });
        },
        test: function test() "gDevTools" in this.window
    },
    "  Web Console": {
        devToolsPane: true,
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be well formatted and useable."],
        ],
        action: function action() {
            this.openToolbox("webconsole", (inspector, toolbox) => {
                let { content } = this.window;
                let { console } = content;
                // Add some interesting stuff to the console.
                ["log", "info", "warn", "error"].forEach(function (meth) {
                    console[meth](meth[0].toUpperCase() + meth.slice(1));
                });
                console.log(true);
                console.log(12);
                console.log(["An", "Array"]);
                console.log({ A: "random", object: "thingy" });
                console.log(content.document.documentElement);
                DOM(content.document.head || content.document.documentElement)
                    .append(["script", { type: "application/javascript" }, "throw Error('I broke!')"]);
            });
        }
    },
    "  Browser Console": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            let { HUDService } = this.window;
            if (!HUDService)
                HUDService = this.window.HUDConsoleUI;

            function getBrowserConsole() HUDService.getBrowserConsole ? HUDService.getBrowserConsole()
                                                                      : HUDService.browserConsole;

            // We want to open the window afresh, even if it's already
            // open, so it comes to the foreground.
            HUDService.toggleBrowserConsole();
            if (!getBrowserConsole())
                HUDService.toggleBrowserConsole();

            this.undo.cleanup.push(() => {
                if (getBrowserConsole())
                    HUDService.toggleBrowserConsole();
            });
        },
        test: function test() {
            let h = this.window.HUDConsoleUI || this.window.HUDService;

            return h && h.toggleBrowserConsole
        },
    },
    "  Scratchpad": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.window.Scratchpad.openScratchpad();
            this.undo.windows.pushMostRecent();
        },
        test: function test() "Scratchpad" in this.window
    },
    "  Style editor window": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.window.StyleEditor.openChrome();
            this.undo.windows.pushMostRecent();
        },
        test: function test() "StyleEditor" in this.window
    },
    "Dialogs": {},
    "  About": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted, with a functional \
                update checker, and the stock Firefox logo for the \
                appropriate release channel."],
        ],
        action: function action() {
            this.window.openAboutDialog();
            this.undo.windows.pushMostRecent();
        }
    },
    "  Customize toolbar": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted and functional. \
                Make sure in particular that no icons display sprite \
                sheets rather than their intended icons."],
        ],
        action: function action() {
            let { gCustomizeMode } = this.window;
            gCustomizeMode.enter();
            this.undo.cleanup.push(() => { gCustomizeMode.exit() });
        },
        test: function test() this.window.gCustomizeMode
    },
    "  Customize toolbar (dialog)": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted and functional. \
                Make sure in particular that no icons display sprite \
                sheets rather than their intended icons."],
        ],
        action: function action() {
            this.setPref("toolbar.customization.usesheet", false);
            this.window.BrowserCustomizeToolbar();
            this.undo.windows.pushMostRecent();
        },
        test: function test() !this.window.gCustomizeMode
    },
    "  Customize toolbar (panel)": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted and functional."],
        ],
        action: function action() {
            this.setPref("toolbar.customization.usesheet", true);
            this.window.BrowserCustomizeToolbar();
        },
        test: function test() !this.window.gCustomizeMode
    },
    "  Places": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "Must be well formatted and useable."],
        ],
        action: function action() {
            let { window } = this;
            window.PlacesCommandHook.showPlacesOrganizer(window.ORGANIZER_ROOT_BOOKMARKS);
            this.undo.windows.pushMostRecent();
        }
    },
    "  Preferences (instant apply)": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted and functional. \
                Must have Close, but not OK or Cancel, buttons. \
                Check all tabs. In the ", ["tt", {}, "Privacy"], " tab's \
                history group check with both ", ["tt", {}, "Remember history"],
                " and ", ["tt", {}, "Use custom settings for history"], " \
                selected."],
        ],
        action: function action() {
            this.setPref("browser.preferences.instantApply", true);
            this.undo.windows.push(this.window.openPreferences());
        }
    },
    "  Preferences (apply on close)": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be formatted properly formatted and functional. \
                Must have Cancel and OK buttons."],
        ],
        action: function action() {
            this.setPref("browser.preferences.instantApply", false);
            this.undo.windows.push(this.window.openPreferences());
        }
    },
    "Other": {},
    "  Find bar": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                Must be well formatted and useable."],
        ],
        action: function action() {
            this.window.gFindBar.onFindCommand();
        },
        cleanup: function cleanup() {
            this.window.gFindBar.close();
        }
    },
    "  Geolocation": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "\
                The geolocation opt-in panel must be properly formatted \
                and have an appropriate icon:"],
            ["img", { src: image(config.haveGecko("14") ? "ff14/geolocation" : "geolocation") }],
        ],
        action: function action() {
            this.setPref("geo.enabled", true);
            this.loadPage("http://www.example.com/", function (browser) {
                browser.contentWindow
                       .navigator.geolocation
                       .getCurrentPosition(function () {});
            });
        }
    },
    "  OS-X Magical Brokenness": {
        get content() ["div", { xmlns: "html" },
            ["p", {}, "\
                The following files are missing from ", ["tt", {}, OSX_BASE2], ":"],
            ["ul", {},
                template.map(
                    OSX_MAGIC.filter(function (file) io.uriExists(OSX_BASE1 + file) && !io.uriExists(OSX_BASE2 + file)),
                    function (name) ["li", {}, name])],
            ["p", {}, "\
                For more information see ",
                ["a", { href: "http://bugzil.la/679708" }, "bug 679708"],
                " and ",
                ["a", { href: "http://bugzil.la/702558" }, "bug 702558"], "."],
        ],
        test: function test() config.OS.isMacOSX
    },
    "  XUL Widgets": {
        content: ["div", { xmlns: "html" },
            ["p", {}, "The following XUL widgets must all be workable:"],
            ["vbox", { xmlns: XUL, style: "width: 100%;", class: "theme-test-widgets" },
                ["hbox", { class: "theme-test-single-wide" },
                    ["tree", { flex: "1", seltype: "single", rows: "4" },
                        ["treecols", {},
                            ["treecol", { primary: "true", flex: "1", label: "Column" }],
                            ["treecol", { flex: "1", label: "Column" }]],
                        ["treechildren", {},
                            ["treeitem", { container: "true", open: "true", selected: "true" },
                                ["treerow", { selected: "true" },
                                    ["treecell", { label: "Parent 1" }],
                                    ["treecell", { label: "Open (select me)" }]],
                                ["treechildren", {},
                                    ["treeitem", {},
                                        ["treerow", {},
                                            ["treecell", { label: "Child 1" }],
                                            ["treecell", { label: "" }]]]]],
                            ["treeitem", { container: "true", open: "false" },
                                ["treerow", {},
                                    ["treecell", { label: "Parent 2" }],
                                    ["treecell", { label: "Closed" }]],
                                ["treechildren", {},
                                    ["treeitem", {},
                                        ["treerow", {},
                                            ["treecell", { label: "Child 2" }],
                                            ["treecell", { label: "" }]]]]],
                            ["treeitem", {},
                                ["treerow", {},
                                    ["treecell", { label: "Row" }],
                                    ["treecell", { label: "" }]]]]]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["separator", { class: "groove-thin" }]],

                ["hbox", {},
                    ["button", { type: "checkbox", label: "Checkbox", checked: "true" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A checked checkbox button" }]]],
                ["hbox", {},
                    ["button", { type: "checkbox", label: "Checkbox" }],
                    ["vbox", { pack: "center" }, ["label", { value: "An unchecked checkbox button" }]]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["separator", { class: "groove" }]],

                ["hbox", {},
                    ["button", { type: "radio", label: "Radio", checked: "true" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A checked radio button" }]]],
                ["hbox", {},
                    ["button", { type: "radio", label: "Radio" }],
                    ["vbox", { pack: "center" }, ["label", { value: "An unchecked radio button" }]]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["separator", { class: "groove-thin" }]],

                ["hbox", {},
                    ["toolbarbutton", { type: "checkbox", label: "Checkbox", checked: "true" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A checked checkbox toolbar button" }]]],
                ["hbox", {},
                    ["toolbarbutton", { type: "checkbox", label: "Checkbox" }],
                    ["vbox", { pack: "center" }, ["label", { value: "An unchecked checkbox toolbar button" }]]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["separator", { class: "groove" }]],

                ["hbox", {},
                    ["toolbarbutton", { type: "radio", label: "Radio", checked: "true" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A checked radio toolbar button" }]]],
                ["hbox", {},
                    ["toolbarbutton", { type: "radio", label: "Radio" }],
                    ["vbox", { pack: "center" }, ["label", { value: "An unchecked radio toolbar button" }]]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["separator", { class: "groove" }]],

                ["hbox", {},
                    ["button", { type: "menu", label: "Menu" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A menu button" }]]],
                ["hbox", {},
                    ["button", { type: "menu-button", label: "Menu-button" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A menu-button" }]]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["separator", { class: "groove" }]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["radiogroup", {},
                        ["radio", { label: "Checked radio", checked: "true" }],
                        ["radio", { label: "Unchecked radio" }]],
                    ["vbox", {},
                        ["checkbox", { label: "Checked checkbox", checked: "true" }],
                        ["checkbox", { label: "Unchecked checkbox" }]]],

                ["hbox", { class: "theme-test-single-wide" }["separator", { class: "groove" }]],

                ["hbox", {},
                    ["progressmeter", { mode: "determined" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A determinate progress meter" }]]],

                ["hbox", {},
                    ["progressmeter", { mode: "undetermined" }],
                    ["vbox", { pack: "center" }, ["label", { value: "A non-determinate progress meter" }]]],

                ["hbox", { class: "theme-test-single-wide" }, ["separator", { class: "groove" }]],

                ["hbox", { class: "theme-test-single-wide" },
                    ["vbox", { flex: "1" },
                        ["vbox", { pack: "center" }, ["label", { value: "Textboxes" }]],
                        ["vbox", { style: "padding-left: 1em", flex: "1" },
                            ["textbox", { type: "autocomplete", autocompletesearch: "history", placeholder: "History autocomplete (type in me)" }],
                            ["textbox", { placeholder: "A plain textbox (I mustn't have borders)", class: "plain" }],
                            ["textbox", { placeholder: "An ordinary textbox" }],
                            ["textbox", { type: "search", placeholder: "A search textbox" }],
                            ["hbox", {},
                                ["vbox", { pack: "center" }, ["label", { value: "A number box:" }]],
                                ["hbox", { flex: "1" }, ["hbox", { flex: "1" }], ["textbox", { type: "number", value: "42", size: "4" }]]],
                            ["hbox", {},
                                ["vbox", { pack: "center" }, ["label", { value: "A password box:" }]],
                                ["hbox", { flex: "1" }, ["hbox", { flex: "1" }], ["textbox", { type: "password", value: "password" }]]]]]],

                ["hbox", { class: "theme-test-single-wide" }["separator", { class: "groove" }]],

                ["hbox", {},
                    ["vbox", { pack: "center" },
                        ["label", { value: "Resizers", id: "theme-test-resizers" }]],
                    ["vbox", { flex: "1" },
                        ["hbox", {},
                              ["separator", { flex: "1" }],
                              ["label", { value: "L:" }], ["resizer", { element: "theme-test-resizers", dir: "left" }],
                              ["separator", { flex: "1" }],
                              ["label", { value: "T:" }], ["resizer", { element: "theme-test-resizers", dir: "top" }],
                              ["separator", { flex: "1" }],
                              ["label", { value: "TL:" }], ["resizer", { element: "theme-test-resizers", dir: "topleft" }],
                              ["separator", { flex: "1" }],
                              ["label", { value: "TR:" }], ["resizer", { element: "theme-test-resizers", dir: "topright" }],
                              ["separator", { flex: "1" }]],
                        ["hbox", {},
                              ["separator", { flex: "1" }],
                              ["label", { value: "B:" }], ["resizer", { element: "theme-test-resizers", dir: "bottom" }],
                              ["separator", { flex: "1" }],
                              ["label", { value: "R:" }], ["resizer", { element: "theme-test-resizers", dir: "right" }],
                              ["separator", { flex: "1" }],
                              ["label", { value: "BL:" }], ["resizer", { element: "theme-test-resizers", dir: "bottomleft" }],
                              ["separator", { flex: "1" }],
                              ["label", { value: "BR:" }], ["resizer", { element: "theme-test-resizers", dir: "bottomright" }],
                              ["separator", { flex: "1" }]],
                        ["hbox", {},
                              ["separator", { flex: "1" }],
                              ["label", { value: "BE:" }], ["resizer", { element: "theme-test-resizers", dir: "bottomend" }],
                              ["separator", { flex: "1" }]]]],

                ["hbox", { class: "theme-test-single-wide" }, ["separator", { class: "groove" }]],

                ["hbox", {},
                    ["hbox", { pack: "center" }, ["spinbuttons"]],
                    ["vbox", { pack: "center" }, ["label", { value: "Spin buttons" }]]]],
        ],
        action: function action() {
            let i = 0;
            let { content } = this.objects;
            let dom = DOM("progressmeter", content);
            this.undo.timers.push(
                services.Timer(function () {
                    i++;
                    dom.each(function (meter) { meter.value = i % 100; });
                }, 10, services.Timer.TYPE_REPEATING_SLACK));
        }
    },
};
